// ZzFX - 超小型声音合成器 - 微版本
// 麻省理工学院开源协议 - 版权归 2019 年 Frank Force 所有
// 项目仓库地址：https://github.com/KilledByAPixel/ZzFX

// 这是 zzfx 的压缩版本，用于代码体积受限的项目。
// 你可以使用 zzfxV 来设置音量。
// 你可以根据自己的需求进一步压缩它！

// 'use strict';

///////////////////////////////////////////////////////////////////////////////

// ZzFXMicro - Zuper Zmall Zound Zynth - v1.1.8

// ==ClosureCompiler==
// @compilation_level ADVANCED_OPTIMIZATIONS
// @output_file_name ZzFXMicro.min.js
// @js_externs zzfx, zzfxG, zzfxP, zzfxV, zzfxX
// @language_out ECMASCRIPT_2019
// ==/ClosureCompiler==

// 生成并播放声音
/**
 * 生成并播放声音的函数，调用 `zzfxG` 生成采样数据并通过 `zzfxP` 播放。
 * @param {...any} z - 生成声音所需的参数。
 * @returns {AudioBufferSourceNode} - 音频源节点。
 */
/**
 * 生成并播放声音的函数，调用 `zzfxG` 生成采样数据并通过 `zzfxP` 播放。
 * @param {...any} z - 生成声音所需的参数。
 * @returns {AudioBufferSourceNode} - 音频源节点。
 */
const zzfx = (...z)=> zzfxP(zzfxG(...z)); // generate and play sound
// 音量设置
/**
 * 全局音量设置，范围从 0 到 1。
 * @type {number}
 */
/**
 * 全局音量设置，范围从 0 到 1。
 * @type {number}
 */
const zzfxV = .3;    // volume
// 采样率设置
/**
 * 音频采样率，单位为赫兹。
 * @type {number}
 */
/**
 * 音频采样率，单位为赫兹。
 * @type {number}
 */
const zzfxR = 44100; // sample rate
// 创建音频上下文
/**
 * 音频上下文对象，用于管理音频处理图。
 * @type {AudioContext}
 */
/**
 * 音频上下文对象，用于管理音频处理图。
 * @type {AudioContext}
 */
const zzfxX = new AudioContext; // audio context
// 播放采样数据
/**
 * 播放采样数据的函数，将采样数据复制到音频缓冲区并播放。
 * @param {...Float32Array} samples - 采样数据数组。
 * @returns {AudioBufferSourceNode} - 音频源节点。
 */
/**
 * 播放采样数据的函数，将采样数据复制到音频缓冲区并播放。
 * @param {...Float32Array} samples - 采样数据数组。
 * @returns {AudioBufferSourceNode} - 音频源节点。
 */
const zzfxP = (...samples) =>  // play samples
{
    // create buffer and source
    let buffer = zzfxX.createBuffer(samples.length, samples[0].length, zzfxR),
        source = zzfxX.createBufferSource();

    // copy samples to buffer and play
    samples.map((d,i)=> buffer.getChannelData(i).set(d));
    source.buffer = buffer;
    source.connect(zzfxX.destination);
    return source;
}

/**
 * 用于管理 ZzFX 声音播放的类，提供设置播放速率、音量、开始和停止播放的方法。
 */
class FunZZfx {
        /**
     * 构造函数，初始化音频缓冲区、音频源和增益节点。
     * @param {...Float32Array} samples - 采样数据数组。
     */
    constructor(...samples) {
        this.samples = samples;

        // create buffer and source
        let buffer = zzfxX.createBuffer(samples.length, samples[0].length, zzfxR);
        this.source = zzfxX.createBufferSource();

        // copy samples to buffer and play
        samples.map((d,i)=> buffer.getChannelData(i).set(d));
        this.source.buffer = buffer;

        this.gainNode = zzfxX.createGain();
        this.gainNode.connect(zzfxX.destination);

        this.source.connect(this.gainNode);
    }

        /**
     * 设置音频播放速率。
     * @param {number} rate - 播放速率，1 为正常速率。
     */
    setRate(rate) {
        this.source.playbackRate.value = rate;
    }

        /**
     * 设置音频音量。
     * @param {number} volume - 音量，范围从 0 到 1。
     */
    setVolume(volume) {
        this.gainNode.gain.value = volume;
    }

        /**
     * 开始播放音频。
     */
    start() {
        this.source.start();
    }

        /**
     * 停止播放音频。
     */
    stop() {
        this.source.stop();
    }
}


// 生成采样数据
/**
 * 生成音频采样数据的函数，根据传入的参数生成不同的声音波形。
 * @param {number} [volume=1] - 音量，范围从 0 到 1。
 * @param {number} [randomness=0.05] - 频率随机偏移量。
 * @param {number} [frequency=220] - 基础频率，单位为赫兹。
 * @param {number} [attack=0] - 声音起音时间，单位为秒。
 * @param {number} [sustain=0] - 声音持续时间，单位为秒。
 * @param {number} [release=0.1] - 声音释音时间，单位为秒。
 * @param {number} [shape=0] - 波形形状，0 为正弦波，1 为三角波，2 为锯齿波，3 为正切波，4 为噪声。
 * @param {number} [shapeCurve=1] - 波形曲线，0 为方波，2 为尖波。
 * @param {number} [slide=0] - 频率滑动量。
 * @param {number} [deltaSlide=0] - 频率滑动变化量。
 * @param {number} [pitchJump=0] - 音高跳跃量。
 * @param {number} [pitchJumpTime=0] - 音高跳跃时间，单位为秒。
 * @param {number} [repeatTime=0] - 重复时间，单位为秒。
 * @param {number} [noise=0] - 噪声强度。
 * @param {number} [modulation=0] - 调制频率。
 * @param {number} [bitCrush=0] - 比特压缩强度。
 * @param {number} [delay=0] - 延迟时间，单位为秒。
 * @param {number} [sustainVolume=1] - 持续阶段的音量。
 * @param {number} [decay=0] - 衰减时间，单位为秒。
 * @param {number} [tremolo=0] - 颤音强度。
 * @returns {Float32Array} - 生成的采样数据数组。
 */
/**
 * 生成音频采样数据的函数，根据传入的参数生成不同的声音波形。
 * @param {number} [volume=1] - 音量，范围从 0 到 1。
 * @param {number} [randomness=0.05] - 频率随机偏移量。
 * @param {number} [frequency=220] - 基础频率，单位为赫兹。
 * @param {number} [attack=0] - 声音起音时间，单位为秒。
 * @param {number} [sustain=0] - 声音持续时间，单位为秒。
 * @param {number} [release=0.1] - 声音释音时间，单位为秒。
 * @param {number} [shape=0] - 波形形状，0 为正弦波，1 为三角波，2 为锯齿波，3 为正切波，4 为噪声。
 * @param {number} [shapeCurve=1] - 波形曲线，0 为方波，2 为尖波。
 * @param {number} [slide=0] - 频率滑动量。
 * @param {number} [deltaSlide=0] - 频率滑动变化量。
 * @param {number} [pitchJump=0] - 音高跳跃量。
 * @param {number} [pitchJumpTime=0] - 音高跳跃时间，单位为秒。
 * @param {number} [repeatTime=0] - 重复时间，单位为秒。
 * @param {number} [noise=0] - 噪声强度。
 * @param {number} [modulation=0] - 调制频率。
 * @param {number} [bitCrush=0] - 比特压缩强度。
 * @param {number} [delay=0] - 延迟时间，单位为秒。
 * @param {number} [sustainVolume=1] - 持续阶段的音量。
 * @param {number} [decay=0] - 衰减时间，单位为秒。
 * @param {number} [tremolo=0] - 颤音强度。
 * @returns {Float32Array} - 生成的采样数据数组。
 */
const zzfxG = // generate samples
(
    // parameters
    volume = 1, randomness = .05, frequency = 220, attack = 0, sustain = 0,
    release = .1, shape = 0, shapeCurve = 1, slide = 0, deltaSlide = 0,
    pitchJump = 0, pitchJumpTime = 0, repeatTime = 0, noise = 0, modulation = 0,
    bitCrush = 0, delay = 0, sustainVolume = 1, decay = 0, tremolo = 0
)=>
{
    // init parameters
    let PI2 = PI*2,
    sign = v => v>0?1:-1,
    startSlide = slide *= 500 * PI2 / zzfxR / zzfxR,
    startFrequency = frequency *= (1 + randomness*2*random() - randomness)
        * PI2 / zzfxR,
    b=[], t=0, tm=0, i=0, j=1, r=0, c=0, s=0, f, length;

    // scale by sample rate
    attack = attack * zzfxR + 9; // minimum attack to prevent pop
    decay *= zzfxR;
    sustain *= zzfxR;
    release *= zzfxR;
    delay *= zzfxR;
    deltaSlide *= 500 * PI2 / zzfxR**3;
    modulation *= PI2 / zzfxR;
    pitchJump *= PI2 / zzfxR;
    pitchJumpTime *= zzfxR;
    repeatTime = repeatTime * zzfxR | 0;

    // generate waveform
    for(length = attack + decay + sustain + release + delay | 0;
        i < length; b[i++] = s)
    {
        if (!(++c%(bitCrush*100|0)))                      // bit crush
        {
            s = shape? shape>1? shape>2? shape>3?         // wave shape
                sin((t%PI2)**3) :                    // 4 noise
                max(min(tan(t),1),-1):     // 3 tan
                1-(2*t/PI2%2+2)%2:                        // 2 saw
                1-4*abs(round(t/PI2)-t/PI2):    // 1 triangle
                sin(t);                              // 0 sin

            s = (repeatTime ?
                    1 - tremolo + tremolo*sin(PI2*i/repeatTime) // tremolo
                    : 1) *
                sign(s)*(abs(s)**shapeCurve) *       // curve 0=square, 2=pointy
                volume * zzfxV * (                        // envelope
                i < attack ? i/attack :                   // attack
                i < attack + decay ?                      // decay
                1-((i-attack)/decay)*(1-sustainVolume) :  // decay falloff
                i < attack  + decay + sustain ?           // sustain
                sustainVolume :                           // sustain volume
                i < length - delay ?                      // release
                (length - i - delay)/release *            // release falloff
                sustainVolume :                           // release volume
                0);                                       // post release

            s = delay ? s/2 + (delay > i ? 0 :            // delay
                (i<length-delay? 1 : (length-i)/delay) *  // release delay
                b[i-delay|0]/2) : s;                      // sample delay
        }

        f = (frequency += slide += deltaSlide) *          // frequency
            cos(modulation*tm++);                    // modulation
        t += f - f*noise*(1 - (sin(i)+1)*1e9%2);     // noise

        if (j && ++j > pitchJumpTime)       // pitch jump
        {
            frequency += pitchJump;         // apply pitch jump
            startFrequency += pitchJump;    // also apply to start
            j = 0;                          // reset pitch jump time
        }

        if (repeatTime && !(++r % repeatTime)) // repeat
        {
            frequency = startFrequency;     // reset frequency
            slide = startSlide;             // reset slide
            j = j || 1;                     // reset pitch jump time
        }
    }

    return b;
}

/**
 * 快捷生成并播放声音的函数。
 * @param {...any} def - 生成声音所需的参数。
 */
sound = (...def) => new FunZZfx(zzfxG(...def)).start();
